查看原文
其他

应用 Bro 软件对 TLS 客户端进行指纹识别

2017-04-24 木无聊偶 看雪学院

本文中,我们将使用 Bro 入侵检测软件(IDS)作为客户端指纹探测技术工具。

众所周知,在最初的 TLS 握手过程(特别常用于浏览器中的 HTTPS 协议)中,会交换一条名为“ClientHello”的消息;在这条消息中,客户端列举了所支持的密码组件(即所谓的密码套件)。

例如,使用 Wireshark 协议解析软件来分析 Linux 系统中的 Firefox50.1.0 浏览器所发送的一条“ClientHello”消息,如下图所示。


有一些网站可以告诉我们所用浏览器的 TLS 协议特征,比如 

有趣的是,不同的TLS客户端往往使用不同的密码套件集合。比如,对于wget命令而言:

$ wget -qO/dev/null https://www.google.es

其握手数据报文(“ClientHello”消息)如下图所示。


如图可知,wget 命令发送了 68 个不同的密码套件(而之前的Firefox浏览器发送了15个),而且两者的次序也不同。

因此,除了其他参数(比如同时存在于消息中的,所用的TLS协议版本),密码套件的数目及次序为我们提供了客户端的指纹。

Firefox 浏览器和 Chrome 浏览器有不同的指纹;同样,Tor 客户端(不用于断点续传)的指纹不同于Python脚本,微软WEB请求 APIs,或者 Delphi 编程实现的 RAT,等等。

事实上,我们可以利用其识别非授权程序,或者检测是否有恶意软件,为了操控或者窃密活动而潜伏伪装成用于组织内部的HTTPS浏览器。

走进 Bro 软件

要解析 ClientHello 消息并不是复杂的事情;可以使用 p0f 插件(参考文章:https://idea.popcount.org/2012-06-17-ssl-fingerprinting-for-p0f/)和专用工具比如FingerprintTLS(参考文章:https://blog.squarelemon.com/tls-fingerprinting/)。正如所料,Bro IDS软件也可以解析TLS协议;我们可以在文件/usr/local/bro/share/bro/base/protocols/ssl/consts.bro中找到所用的若干常量。

TLS 协议版本幻数和对应的名字映射如下:

        [...]

        const SSLv2  = 0x0002;

        const SSLv3  = 0x0300;

        const TLSv10 = 0x0301;

        const TLSv11 = 0x0302;

        const TLSv12 = 0x0303;

        const TLSv13 = 0x0304;

        [...]

        const version_strings: table[count] of string = {

                [SSLv2] = "SSLv2",

                [SSLv3] = "SSLv3",

                [TLSv10] = "TLSv10",

                [TLSv11] = "TLSv11",

                [TLSv12] = "TLSv12",

                [TLSv13] = "TLSv13",

                [DTLSv10] = "DTLSv10",

                [DTLSv12] = "DTLSv12"

        } &default=function(i: count):string

        [...]

同样,在文件略微偏后的位置处,能看到不同的密码套件和cipher_desc数据结构:

[...]

const TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A;

const TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B;

const TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C;

const TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D;

const TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E;

[...]

const cipher_desc: table[count] of string = {

[SSLv20_CK_RC4_128_EXPORT40_WITH_MD5] =

       "SSLv20_CK_RC4_128_EXPORT40_WITH_MD5",

[SSLv20_CK_RC4_128_WITH_MD5] = "SSLv20_CK_RC4_128_WITH_MD5",

[SSLv20_CK_RC2_128_CBC_WITH_MD5] = "SSLv20_CK_RC2_128_CBC_WITH_MD5",

[SSLv20_CK_RC2_128_CBC_EXPORT40_WITH_MD5] =

 "SSLv20_CK_RC2_128_CBC_EXPORT40_WITH_MD5",

[...]

如前文(网址:https://www.securityartwork.es/2017/01/11/bro-ids-tips-and-tricks/)所述,当一个感兴趣的事件发生时,Bro软件捕获一个对应于ClientHello消息的事件,如下所示(定义于文件/usr/local/bro/share/bro/base/bif/plugins/Bro_SSL.events.bif.bro):

global ssl_client_hello: event(

    c: connection,

    version: count,

    possible_ts: time,

    client_random: string,

    session_id: string,

    ciphers: index_vec );

至此,我们已经备齐了所有用于执行指纹识别的材料;脚本框架如下所示:

event ssl_client_hello (c: connection,

           version: count,

           possible_ts: time,

           client_random: string,

           session_id: string,

           ciphers: index_vec)

       {

           local ciphers_str: vector of string;

  

           for (i in ciphers) {

               ciphers_str[i] = SSL::cipher_desc[ciphers[i]];

           }

  

           print fmt("%s %s", SSL::version_strings[version],

               join_string_vec(ciphers_str, ","));

       }

事件原型中的 ciphers 向量所存放的索引,可用于访问消息中的不同密码套件,同时我们可以用 cipher_desc 结构表将它们映射到对应的文本字符串。我们使用join_string_vec() 函数(类似于 Perl,Python 或者其他语言的 join() 函数/方法)来以逗号分隔的方式显示它们。若将脚本保存到文件 log_cipher_suites.bro,则我们在之前所捕获的文件 tls.pcap 上应用该脚本来实现一次快速测试。具体命令与输出如下所示:

 $ /usr/local/bro/bin/bro -C -r tls.pcap log_cipher_suites.bro

    TLSv12 

    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA

    TLSv12 

    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA

可以看出,Firefox 浏览器使用 TLSv1.2 协议发送了 15 个密码套件。

日志记录框架

执行 print 命令,则当Bro软件作为守护进程运行时所有信息将被存储到stdout.log文件中;然而,如果我们想要将输出发送到另一个文件中,或者发送到Elasticsearch搜索引擎呢?为此,Bro软件提供了日志记录框架。其用法非常简单,一学就会;-)

要讲之前的“测试”脚本改造成最终版本,首先我们需要创建一个名为TlsFingerprint的模块,命令如下:

module TlsFingerprint;

然后,我们需要定义两个变量,它们将被模块导出到Bro软件的其他部分;简便起见,将其命名为 LOG 和 Info。具体代码如下:

export {

           redef enum Log::ID += { LOG };

  

           type Info: record {

               ts: time &log;

               uid: string &log;

               id: conn_id &log;

               tls_version: string &log;

               ciphers: vector of string &log;

           };

       }

可见,Info 是将被记录于文件中的条目。每一条日志记录包含时间戳(采用常用的Unix 时间戳格式),一对标识符,TLS 协议版本以及不同的密码套件。记录使用 Bro 数据类型定义,如 vector of string,而框架负责将其自动存储到文本文件,搜索引擎或者之前配置的其他后端。

为了指定日志文件名称,我们拦截 Bro 软件的初始化进程(bro_init() 事件),然后调用 Log 模块(日志记录框架代码存在于其中)的 create_stream() 函数。具体代码如下:

event bro_init() &priority=5

       {

           Log::create_stream(TlsFingerprint::LOG, [$columns=Info, $path="tls_finger"]);

       }

由此,文件将被命名为“tls_finger.log”,将在Bro日志文件夹中创建并将自动更新。

最后,针对 ssl_client_hello() 事件所添加的代码如下所示:

event ssl_client_hello (c: connection,

           version: count,

           possible_ts: time,

           client_random: string,

           session_id: string,

           ciphers: index_vec)

       {

           local ciphers_str: vector of string;

           local rec: TlsFingerprint::Info;

  

           for (i in ciphers) {

               ciphers_str[i] = SSL::cipher_desc[ciphers[i]];

           }

  

           rec$ts = network_time();

           rec$uid = c$uid;

           rec$id = c$id;

           rec$tls_version = SSL::version_strings[version];

           rec$ciphers = ciphers_str;

  

           Log::write(TlsFingerprint::LOG, rec);

       }

如上所见,我们定义变量rec,其格式为之前所声明的(TlsFingerprint::Info)记录类型,然后填充相应的值,最后调用函数Log::write()。Bro软件已经知道如何恰当地在日志文件或者相应的后端中表示数据类型(比如connection或vector of string),分隔各个域(源IP地址,源端口,目的IP地址,目的端口等)。

最终的 tls_finger.bro 内容如下所示:

module TlsFingerprint;

       export {

           redef enum Log::ID += { LOG };

           type Info: record {

               ts: time &log;

               uid: string &log;

               id: conn_id &log;

               tls_version: string &log;

               ciphers: vector of string &log;

           };

       }

       event bro_init() &priority=5

       {

           Log::create_stream(TlsFingerprint::LOG, [$columns=Info, $path="tls_finger"]);

       }

       event ssl_client_hello (c: connection,

           version: count,

           possible_ts: time,

           client_random: string,

           session_id: string,

           ciphers: index_vec)

       {

           local ciphers_str: vector of string;

           local rec: TlsFingerprint::Info;

           for (i in ciphers) {

               ciphers_str[i] = SSL::cipher_desc[ciphers[i]];

           }

           rec$ts = network_time();

           rec$uid = c$uid;

           rec$id = c$id;

           rec$tls_version = SSL::version_strings[version];

           #rec$ciphers = join_string_vec(ciphers_str, ",");

           rec$ciphers = ciphers_str;

           Log::write(TlsFingerprint::LOG, rec);

       }

若我们将改文件置于目录 /usr/local/share/bro/site 之下,则可以使用以下命令将其从 local.bro 中加载:

@load site/tls_finger

经过相应的 broctl deploy 配置之后,日志文件中的条目将如下所示:

#fields ts         uid                id.orig_h    id.orig_p    id.resp_h    id.resp_p   tls_version     ciphers

#types  time       string             addr         port         addr         port        string          vector[string]

1485109320.798053  CxDC4MuavmTCRktJ3  192.168.1.1  52659        192.168.1.2  443         TLSv10          TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_RC4_128_MD5

当我们获得更多的信息时,就该对其进行分析以发现异常了。比如,按照最少使用到最多使用的次序对密码套件集合排序,具体命令如下所示:

$ cat tls_finger.log | awk -F"\t" '{ print $8 }' | sort | uniq -c | sort –n

当然,优秀的恶意软件会尽力地潜伏,模仿控制工作站中常用浏览器的TLS协议指纹,但我们或许可以抓到一些漏网之鱼。


本文由 看雪翻译小组 木无聊偶 编译,来源 Pablo M.@securityartwork


戳👇 图片加入看雪翻译小组哦!


❤ 往期热门内容推荐



更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!

看雪论坛:http://bbs.pediy.com/

微信公众号 ID:ikanxue

微博:看雪安全

投稿、合作:www.kanxue.com

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存